// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.impl.compiled;

import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.ElementPresentationUtil;
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.impl.java.stubs.PsiParameterStub;
import com.intellij.psi.impl.java.stubs.impl.PsiParameterStubImpl;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.ui.IconManager;
import com.intellij.ui.PlatformIcons;
import com.intellij.ui.icons.RowIcon;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;

public final class ClsParameterImpl extends ClsRepositoryPsiElement<PsiParameterStub> implements PsiParameter {
  private final NotNullLazyValue<PsiTypeElement> myType;
  private volatile String myMirrorName;

  public ClsParameterImpl(@NotNull PsiParameterStub stub) {
    super(stub);
    myType = NotNullLazyValue.atomicLazy(() -> {
      return new ClsTypeElementImpl(this, getStub().getType());
    });
  }

  @Override
  public PsiIdentifier getNameIdentifier() {
    return null;
  }

  @Override
  public @NotNull String getName() {
    return CachedValuesManager.getCachedValue(this, () -> {
      return CachedValueProvider.Result.create(calcName(), PsiModificationTracker.MODIFICATION_COUNT);
    });
  }

  @Override
  public PsiElement @NotNull [] getChildren() {
    return getChildren(getModifierList(), getTypeElement());
  }

  private @NotNull String calcName() {
    PsiParameterStubImpl parameterStub = (PsiParameterStubImpl)getStub();
    if (!parameterStub.isAutoGeneratedName()) {
      return parameterStub.getName();
    }

    ClsMethodImpl method = (ClsMethodImpl)getDeclarationScope();
    PsiMethod sourceMethod = method.getSourceMirrorMethod();
    if (sourceMethod != null) {
      assert sourceMethod != method : method;
      return sourceMethod.getParameterList().getParameters()[getIndex()].getName();
    }

    return getMirrorName();
  }

  public boolean isAutoGeneratedName() {
    return ((PsiParameterStubImpl)getStub()).isAutoGeneratedName() &&
           ((ClsClassImpl)getDeclarationScope().getParent()).getSourceMirrorClass() == null;
  }

  @Override
  public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
    throw cannotModifyException(this);
  }

  @Override
  public @NotNull PsiTypeElement getTypeElement() {
    return myType.getValue();
  }

  @Override
  public @NotNull PsiType getType() {
    return getTypeElement().getType();
  }

  @Override
  public @NotNull PsiModifierList getModifierList() {
    final StubElement<PsiModifierList> child = getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST);
    assert child != null;
    return child.getPsi();
  }

  @Override
  public boolean hasModifierProperty(@NotNull String name) {
    return getModifierList().hasModifierProperty(name);
  }

  @Override
  public PsiExpression getInitializer() {
    return null;
  }

  @Override
  public boolean hasInitializer() {
    return false;
  }

  @Override
  public Object computeConstantValue() {
    return null;
  }

  @Override
  public void normalizeDeclaration() throws IncorrectOperationException {
  }

  @Override
  public void appendMirrorText(int indentLevel, @NotNull StringBuilder buffer) {
    PsiAnnotation[] annotations = getModifierList().getAnnotations();
    for (PsiAnnotation annotation : annotations) {
      appendText(annotation, indentLevel, buffer);
      buffer.append(' ');
    }
    appendText(getTypeElement(), indentLevel, buffer, " ");
    buffer.append(getMirrorName());
  }

  private @NotNull String getMirrorName() {
    String mirrorName = myMirrorName;
    if (mirrorName == null) {
      // parameter name may depend on a name of a previous one in a same parameter list
      synchronized (getParent()) {
        mirrorName = myMirrorName;
        if (mirrorName == null) {
          myMirrorName = mirrorName = calcNiceParameterName();
        }
      }
    }
    return mirrorName;
  }

  private @NotNull String calcNiceParameterName() {
    String name = null;

    PsiParameterStubImpl stub = (PsiParameterStubImpl)getStub();
    if (!stub.isAutoGeneratedName()) {
      name = stub.getName();
    }

    if (name == null) {
      name = StringUtil.notNullize(JavaCodeStyleManager.getInstance(getProject()).suggestCompiledParameterName(getType()), "p");

      String base = name;
      int n = 0;
      AttemptsLoop:
      while (true) {
        for (PsiParameter parameter : ((PsiParameterList)getParent()).getParameters()) {
          if (parameter == this) break AttemptsLoop;
          String prevName = ((ClsParameterImpl)parameter).getMirrorName();
          if (name.equals(prevName)) {
            name = base + (++n);
            continue AttemptsLoop;
          }
        }
      }
    }

    return name;
  }

  @Override
  protected void setMirror(@NotNull TreeElement element) throws InvalidMirrorException {
    setMirrorCheckingType(element, null);

    PsiParameter mirror = SourceTreeToPsiMap.treeToPsiNotNull(element);
    setMirror(getModifierList(), mirror.getModifierList());
    setMirror(getTypeElement(), mirror.getTypeElement());
  }

  @Override
  public void accept(@NotNull PsiElementVisitor visitor) {
    if (visitor instanceof JavaElementVisitor) {
      ((JavaElementVisitor)visitor).visitParameter(this);
    }
    else {
      visitor.visitElement(this);
    }
  }

  @Override
  public @NotNull PsiElement getDeclarationScope() {
    // only method parameters exist in compiled code
    return getParent().getParent();
  }

  private int getIndex() {
    final PsiParameterStub stub = getStub();
    return stub.getParentStub().getChildrenStubs().indexOf(stub);
  }

  @Override
  public boolean isVarArgs() {
    final PsiParameterList paramList = (PsiParameterList)getParent();
    final PsiMethod method = (PsiMethod)paramList.getParent();
    return method.isVarArgs() && getIndex() == paramList.getParametersCount() - 1;
  }

  @Override
  protected boolean isVisibilitySupported() {
    return true;
  }

  @Override
  public Icon getElementIcon(final int flags) {
    IconManager iconManager = IconManager.getInstance();
    RowIcon baseIcon = iconManager.createLayeredIcon(this, iconManager.getPlatformIcon(PlatformIcons.Parameter), 0);
    return ElementPresentationUtil.addVisibilityIcon(this, flags, baseIcon);
  }

  @Override
  public @NotNull SearchScope getUseScope() {
    return new LocalSearchScope(getDeclarationScope());
  }

  @Override
  public @NotNull PsiElement getNavigationElement() {
    PsiParameterList list = ObjectUtils.tryCast(getParent(), PsiParameterList.class);
    if (list != null) {
      PsiMethod clsMethod = ObjectUtils.tryCast(list.getParent(), PsiMethod.class);
      if (clsMethod != null) {
        int index = ArrayUtil.indexOf(list.getParameters(), this);
        if (index >= 0) {
          PsiMethod psiMethod = ObjectUtils.tryCast(clsMethod.getNavigationElement(), PsiMethod.class);
          if (psiMethod != null && psiMethod != clsMethod) {
            PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters();
            if (psiParameters.length > index) {
              return psiParameters[index];
            }
          }
        }
      }
    }
    return this;
  }

  @Override
  public String toString() {
    return "PsiParameter:" + getName();
  }
}
